// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: jingyuan
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/join.hpp>
#include <fmt/core.h>
#include <gflags/gflags.h>

#include "cc/appcoin2/support/account_control/approach_target_position_strategy.h"
#include "coin2/app/driver.h"

DEFINE_bool(hard_close, false, "perform hard close");
DEFINE_bool(cross_mode, false, "cross_mode for huobi usdt");
DEFINE_bool(post_at_mid, false, "post at mid rather than bbo, useful for illiquid pairs");
DEFINE_bool(use_target_position_as_delta, false, "treat target position as quantity to buy");
DEFINE_double(auto_shutdown_sec, 300, "auto shutdown after x sec, default is 5 mins");
DEFINE_double(fill_cooldown_sec, 3, "1 lot of fill per x sec");
DEFINE_double(tick_qty, -1, "override tick qty here");
DEFINE_double(lot_size, 0.0, "Lot size for each post");
DEFINE_double(order_update_period, 1.0, "order_update_period sec");
DEFINE_double(pass_bp, 5.0, "passive bp for bid ask price.");
DEFINE_double(target_position, 0.0, "Target position to approach.");
DEFINE_double(leverage, 0.0, "leverage for futures");
DEFINE_double(stickiness, 2.0, "stickiness in bp");
DEFINE_double(max_posting_period, 60.0, "max_posting_period_sec");
DEFINE_double(market_share_limit_sec, 0.0, "market share limit window period");
DEFINE_double(market_share_limit, 0, "market share limit");
DEFINE_double(limit_payup_bp_to_ma1h, 100, "limit payup to 1h ma window");
DEFINE_bool(ignore_self_order, true, "avoid self echo on order");
DEFINE_string(rounding_policy_str, "TICK_ROUND_INWARD", "");

DEFINE_double(min_price, 0, "min price");
DEFINE_double(max_price, 0, "max price");

DEFINE_string(strategy_name, "approach_target_position", "strategy name");
DEFINE_string(mea, "", "mea and recipe e.g. Spot.Binance.v1.snapshot");
DEFINE_string(symbol, "", "symbol to trade");
DEFINE_string(key_path, "", "path to keyfile");
DEFINE_string(driver, "", "default driver path");

DEFINE_double(
    round_duration_minute,
    5,
    "limit qty: fill_qty <= {lot_size} / (min_elapsed / {round_duration_minute})");
DEFINE_bool(
    round_hit_taker,
    false,
    "take when: fill_qty < {lot_size} / (min_elapsed / {round_duration_minute})");
DEFINE_double(
    taker_cooldown_second,
    300,
    "take when: timestamp - timestamp_last_order > taker_cooldown_second");
DEFINE_double(taker_payup_bp, 10.0, "taker_payup_bps");

void setLiveConfig(coin2::app::DriverConfig* driverConfig) {
  auto* mutable_feed = driverConfig->mutable_feed();

  mutable_feed->mutable_common()->set_type(
      ::coin2::exchange::feed::FeedSystemType::FEED_SYSTEM_TYPE_LIVE);
  coin2::exchange::feed::FeedSystemConfig feedSytemConfig;
  std::vector<std::string> mea_list;
  boost::split(mea_list, FLAGS_mea, boost::is_any_of("."));
  CHECK_EQ(mea_list.size(), 4) << "mea should be Spot.Binance.v1.snapshot";
  auto recipe = mea_list.back();

  if (FLAGS_ignore_self_order) {
    CHECK_EQ(recipe.find("l1"), std::string::npos)
        << "recipe should use full-book recipe like realtime/snapshot when exclude self order";
    CHECK_EQ(recipe.find("bbo"), std::string::npos)
        << "recipe should use full-book recipe like realtime/snapshot when exclude self order";
  }
  mea_list.pop_back();
  auto mea = boost::algorithm::join(mea_list, ".");

  if (driverConfig->mutable_feed()->mutable_exchanges()->count(FLAGS_mea) > 0) {
    feedSytemConfig = (*driverConfig->mutable_feed()->mutable_exchanges())[FLAGS_mea];
  }

  feedSytemConfig.set_recipe(recipe);
  feedSytemConfig.set_mea(mea);
  feedSytemConfig.mutable_products()->add_norms(FLAGS_symbol);
  feedSytemConfig.mutable_websocket()->set_num_workers(1);
  feedSytemConfig.mutable_websocket()->add_key_filepaths(FLAGS_key_path);
  if (FLAGS_mea.find("Okex") != std::string::npos) {
    feedSytemConfig.mutable_websocket()->set_require_auth(true);
  }
  (*driverConfig->mutable_feed()->mutable_exchanges())[FLAGS_mea] = feedSytemConfig;

  coin2::exchange::order::OrderSystemConfig orderSystemConfig;
  if (driverConfig->mutable_order()->mutable_exchanges()->count(FLAGS_mea) > 0) {
    orderSystemConfig = (*driverConfig->mutable_order()->mutable_exchanges())[FLAGS_mea];
  }
  orderSystemConfig.set_mea(mea);
  orderSystemConfig.set_type(coin2::exchange::order::OrderSystemType::ORDER_SYSTEM_TYPE_LIVE_OG);
  orderSystemConfig.mutable_products()->add_norms(FLAGS_symbol);
  orderSystemConfig.mutable_connection_config()->set_key_filepath(FLAGS_key_path);
  orderSystemConfig.mutable_connection_config()->set_use_cross_mode(FLAGS_cross_mode);
  (*driverConfig->mutable_order()->mutable_exchanges())[FLAGS_mea] = orderSystemConfig;

  driverConfig->mutable_strategy()->set_strategy_name(FLAGS_strategy_name);

  std::cout << driverConfig->DebugString();
  std::cout << driverConfig->order().exchanges().cbegin()->second.mea();
  std::cout << driverConfig->order().exchanges().cbegin()->second.products().norms()[0];
}

int main(int argc, char* argv[]) {
  google::InitGoogleLogging(argv[0]);
  gflags::ParseCommandLineFlags(&argc, &argv, true);
  coin2::app::DriverConfig app_config;
  if (FLAGS_driver == "") {
    setLiveConfig(&app_config);
  } else {
    auto path = std::experimental::filesystem::path(FLAGS_driver);
    app_config.MergeFrom(coin2::base::config::DriverConfigFromJson(path));
    setLiveConfig(&app_config);
  }
  coin2::appcoin2::support::account_control::ApproachTargetPositionStrategy strategy(
      FLAGS_pass_bp,
      FLAGS_target_position,
      FLAGS_use_target_position_as_delta,
      FLAGS_lot_size,
      FLAGS_leverage,
      FLAGS_hard_close,
      FLAGS_post_at_mid,
      FLAGS_order_update_period,
      FLAGS_max_posting_period,
      FLAGS_stickiness,
      FLAGS_tick_qty,
      FLAGS_min_price,
      FLAGS_max_price,
      FLAGS_auto_shutdown_sec,
      FLAGS_fill_cooldown_sec,
      FLAGS_round_duration_minute,
      FLAGS_round_hit_taker,
      FLAGS_taker_cooldown_second,
      FLAGS_taker_payup_bp,
      FLAGS_market_share_limit_sec,
      FLAGS_market_share_limit,
      FLAGS_limit_payup_bp_to_ma1h,
      FLAGS_ignore_self_order,
      FLAGS_rounding_policy_str);

  coin2::app::Driver driver(app_config, &strategy);
  driver.RegisterCleanupHandler();
  driver.Run();
  return 0;
}
